using MemoryPack;
using System.Collections.Generic;
using UnityEngine;

namespace Game.Networking.Server {
	public sealed class ServerAgentsController : ServerSideController<ServerAgentsController> {
		public const string StateFileName = "agents.info";
		public const uint KillScore = 200;

		const float AgentRespawnTimeout = 10f;
		ServerAgentsData _state = new ServerAgentsData();

		public ServerAgentsController(ServerGameManager owner) : base(owner) {
		}

		public override void Load() {
			base.Load();
			var loaded = ServerSaveLoadController.Instance.LoadSaveFile<ServerAgentsData>(StateFileName);
			if ( loaded != null ) {
				_state = loaded;
			}
		}

		public override void Save() {
			base.Save();
			ServerSaveLoadController.Instance.SaveFileToDisk(StateFileName, _state);
		}

		public override void Update() {
			base.Update();
		}

		public override void RareUpdate() {
			base.RareUpdate();
			CheckRespawnPossibility();
		}

		public Entity SpawnAgent(string owner, uint unitId, TeamType team ) {
			return SpawnAgent(owner, unitId, team, GetSpawnPosition(team));
		}

		public Entity SpawnAgent(string owner, uint unitId, TeamType team, Vector3 initialPosition) {
			var isAnotherAlive = IsAgentEntityAlive(unitId);
			if ( isAnotherAlive) {
				Debug.LogWarning($"Another agent is already attached to {unitId} computing unit. Destroy it first");
				return null;
			}
			AgentInfo info;
			if ( _state.Agents.ContainsKey(unitId) ) {
				info = _state.Agents[unitId];
			} else {
				info = new AgentInfo();
				_state.Agents.Add(unitId, info);
			}
			RemoveRespawnTimeForAgent(unitId);
			info.AgentOwnerName= owner;
			info.AgentUnitId= unitId;
			var e = ServerEntityController.Instance.CreateNewEntity(EntityType.Ship, initialPosition, Vector3.zero, e => {
				var cast = (ShipCore)e;
				if ( cast != null ) {
					cast.OwnerComputerId = unitId;
					cast.OwnerName = owner;
					cast.Team = team;
				}
			});
			info.AgentEntityId = e.ID;
			var unit = ServerScriptController.Instance.GetUnitById(unitId);
			if ( unit != null ) {
				unit.UpdateAttachedEntityId(e.ID);
			}
			return e;
		}

		public void OnAgentEntityDestroyed(uint unitId) {
			var info = GetAgentInfo(unitId);
			if ( info == null ) {
				return;
			}
			var unit = ServerScriptController.Instance.GetUnitById(unitId);
			if ( unit != null ) {
				unit.UpdateAttachedEntityId(0);
			}
			_state.Agents.Remove(unitId);
			SetRespawnTimeForAgent(unitId, ServerGameManager.Time + AgentRespawnTimeout);
			AddAgentDeathCount(unitId);
		}

		public void DestroyAgent(uint unitId) {
			var info = GetAgentInfo(unitId);
			if ( info == null ) {
				return;
			}
			_state.Agents.Remove(unitId);
			ServerEntityController.Instance.DestroyEntity(info.AgentEntityId);
			AddAgentDeathCount(unitId);
		}

		public bool IsAgentEntityAlive(uint unitId) {
			var entity = GetAgentEntity(unitId);
			return entity && entity.IsEntityAlive;
		}

		//В принципе, можно сделать и интерфейс, но это оверинжиниринг на данный момент, у нас только один тип скриптуемых сущностей
		public ShipCore GetAgentEntity(uint unitId) {
			var info = GetAgentInfo(unitId);
			if ( info == null ) {
				return null;
			}
			var se = ServerEntityController.Instance;
			var entity = se.GetEntityById(info.AgentEntityId);
			return (ShipCore) entity;
		}

		public AgentInfo GetAgentInfo(uint unitId) {
			if ( _state.Agents.TryGetValue(unitId, out AgentInfo agentInfo)) {
				return agentInfo;
			}
			return null;
		}

		public uint GetAgentEntityOwnerUnitId(uint entityId) {
			//TODO: dict maybe?
			foreach ( var a in _state.Agents ) {
				if ( a.Value.AgentEntityId== entityId ) {
					return a.Value.AgentUnitId;
				}
			}
			return 0; //not found
		}

		public void AddScoreToAgentOwner(uint entityId, uint score) {
			var unitId = GetAgentEntityOwnerUnitId(entityId);
			if ( unitId == 0 ) {
				return;
			}
			var stats = GetAgentStats(unitId);
			if ( stats == null ) {
				return;
			}
			stats.Score += score;
		}

		public void AddAgentDeathCount(uint unitId ) {
			var stats = GetAgentStats(unitId);
			if ( stats == null ) {
				return;
			}
			stats.DeathCount += 1;
		}

		public void AddDeathCountToAgentOwner(uint entityId) {
			var unitId = GetAgentEntityOwnerUnitId(entityId);
			if ( unitId == 0 ) {
				return;
			}
			AddAgentDeathCount(unitId);
		}

		public void AddKillCountToAgentOwner(uint entityId) {
			var unitId = GetAgentEntityOwnerUnitId(entityId);
			if ( unitId == 0 ) {
				return;
			}
			AddAgentKillCount(unitId);
			AddScoreToAgentOwner(unitId, KillScore);
			var unit = ServerScriptController.Instance.GetUnitById(unitId);
			if ( unit != null ) {
				var team = ServerController.Instance.GetPlayerTeam(unit.OwnerUser);
				var b = ServerGameController.Instance.GetBaseForTeam(team);
				if ( b != null ) {
					b.AddScore(KillScore);
				}
			}
		}

		public void AddAgentKillCount(uint unitId) {
			var stats = GetAgentStats(unitId);
			if ( stats == null ) {
				return;
			}
			stats.KillCount += 1;
		}

		public AgentStats GetAgentStats(uint unitId) {
			if ( !_state.Stats.TryGetValue(unitId, out AgentStats agentStats)) {
				_state.Stats.Add(unitId, agentStats = new AgentStats());
			}
			return agentStats;
		}
		

		bool _isSpawnListEnumerating = false;
		List<uint> _spawnListRemList= new List<uint>();
		void CheckRespawnPossibility() {
			foreach ( var item in _spawnListRemList ) {
				if ( _state.RespawnTimes.ContainsKey(item) ) {
					_state.RespawnTimes.Remove(item);
				}
			}
			_spawnListRemList.Clear();
			_isSpawnListEnumerating = true;
			foreach ( var kvp in _state.RespawnTimes ) {
				if ( ServerGameManager.Time > kvp.Value ) {
					var unit = ServerScriptController.Instance.GetUnitById(kvp.Key);
					if ( unit == null ) {
						return;
					}
					if ( unit.State == ComputingUnitState.RuntimeError || unit.State == ComputingUnitState.CompileError || unit.State == ComputingUnitState.BudgetExceeded ) {
						return;
					}
					var team = ServerController.Instance.GetPlayerTeam(unit.OwnerUser);
					SpawnAgent(unit.OwnerUser, kvp.Key, team, GetSpawnPosition(team));
					if ( unit.State == ComputingUnitState.Active || unit.State == ComputingUnitState.NoTargetEntity || unit.State == ComputingUnitState.Paused ) {
						unit.State = ComputingUnitState.Ready;
					}					
					_spawnListRemList.Add(kvp.Key);
				}
			}
			_isSpawnListEnumerating = false;
			foreach ( var item in _spawnListRemList ) {
				if ( _state.RespawnTimes.ContainsKey(item) ) {
					_state.RespawnTimes.Remove(item);
				}
			}
			_spawnListRemList.Clear();
		}

		void SetRespawnTimeForAgent(uint unitId, double nextRespawnTime) {
			if ( _state.RespawnTimes.ContainsKey(unitId) ) {
				_state.RespawnTimes[unitId] = nextRespawnTime;
			} else {
				_state.RespawnTimes.Add(unitId, nextRespawnTime);
			}
		}

		void RemoveRespawnTimeForAgent(uint unitId) {
			if ( _isSpawnListEnumerating ) {
				_spawnListRemList.Add(unitId);
				return;
			}
			if ( _state.RespawnTimes.ContainsKey(unitId) ) {
				_state.RespawnTimes.Remove(unitId);
			}
		}

		Vector3 GetSpawnPosition(TeamType team) {
			var baseEntity = ServerGameController.Instance.GetBaseForTeam(team);
			if ( baseEntity != null ) {
				return baseEntity.GetNewSpawnPos();
			}
			if ( team == TeamType.Red ) {
				return new Vector3(-100,0, 0);
			}
			if ( team == TeamType.Blue) {
				return new Vector3(100,0, 0);
			}
			return Vector3.zero;
		}
	}

	[MemoryPackable]
	public partial class AgentInfo {
		public string AgentOwnerName;
		public uint   AgentEntityId;
		public uint   AgentUnitId;
	}
	[MemoryPackable]
	public partial class AgentStats {
		public uint Score = 0;
		public uint KillCount = 0;
		public uint DeathCount = 0;
	}

	[MemoryPackable]
	public partial class ServerAgentsData {
		public Dictionary<uint, double>    RespawnTimes = new Dictionary<uint, double>();
		public Dictionary<uint, AgentInfo> Agents       = new Dictionary<uint, AgentInfo>(); //uint: unit ID (Not entity ID)
		public Dictionary<uint, AgentStats> Stats       = new Dictionary<uint, AgentStats>();//uint: unit ID (Not entity ID)
	}
}